﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.Threading;
using System.Xml;
using log4net;

namespace CSharpExt.Time.Scheduler
{
    // ======================================================================
    // Schedule
    // ======================================================================

    public class Schedule
    {
        #region Private Data

        private List<Trigger> _triggers = new List<Trigger>();

        private List<Task> _tasks = new List<Task>();

        #endregion

        #region Operations

        public Schedule AddTrigger(Trigger t)
        {
            _triggers.Add(t);
            return this;
        }

        public Schedule AddTask(Task t)
        {
            _tasks.Add(t);
            return this;
        }

        public bool IsValid(DateTime now)
        {
            return SearchTrigger(now) != null;
        }

        internal void Execute(ScheduleContext context)
        {
            Trigger trigger = SearchTrigger(context.Now);
            if (trigger == null)
                return;

            context.SetTrigger(trigger);

            foreach (Task task in _tasks)
            {
                context.SetTask(task);
                task.Execute(context);
            }
        }

        #endregion

        #region Implementation

        internal Trigger SearchTrigger(DateTime now)
        {
            foreach (Trigger t in _triggers)
                if (t.IsValid(now))
                    return t;
            return null;
        }

        #endregion
    }

    // ======================================================================
    // ScheduleContext
    // ======================================================================

    public class ScheduleContext
    {
        #region Private Data

        private Scheduler _scheduler;
        private DateTime _now;
        private Trigger _trigger;
        private Task _task;

        #endregion

        #region Constructor

        internal ScheduleContext(Scheduler s, DateTime n)
        {
            _scheduler = s;
            _now = n;
        }

        #endregion

        #region Properties

        public Scheduler Scheduler
        {
            get { return _scheduler; }
        }

        public DateTime Now
        {
            get { return _now; }
        }

        public Trigger Trigger
        {
            get { return _trigger; }
        }

        public Task Task
        {
            get { return _task; }
        }

        #endregion

        #region Implementation

        internal void SetTrigger(Trigger t)
        {
            _trigger = t;
        }

        internal void SetTask(Task t)
        {
            _task = t;
        }

        #endregion
    }

    // ======================================================================
    // SchedulerSelection
    // ======================================================================

    internal class ScheduleSelection
    {
        #region Privat Data

        private DateTime _now;
        private List<Schedule> _schedules = new List<Schedule>();

        #endregion

        #region Constructor

        internal ScheduleSelection(DateTime now)
        {
            _now = now;
        }

        #endregion

        #region Properties

        internal DateTime Now
        {
            get { return _now; }
        }

        internal bool IsValid
        {
            get { return _schedules.Count > 0; }
        }

        internal List<Schedule> Schedules
        {
            get { return _schedules; }
        }

        #endregion

        #region Operations

        internal void AddSchedule(Schedule s)
        {
            _schedules.Add(s);

        }

        #endregion
    }

    // ======================================================================
    // Scheduler
    // ======================================================================

    public class Scheduler
    {
        #region Private Data

        private static readonly TimeSpan ONE_SECOND = new TimeSpan(0, 0, 0, 1);
        private static ILog _logger = LogManager.GetLogger(typeof (Scheduler));

        private volatile bool _terminated;
        private Thread _thread;
        private DateTime _lastCheck;

        private List<Schedule> _schedules = new List<Schedule>();

        #endregion

        #region Operations

        public void Initialize()
        {

        }

        public void AddSchedule(Schedule schedule)
        {
            lock (_schedules)
            {
                _schedules.Add(schedule);
            }
        }

        public void Start()
        {
            _thread = new Thread(checkSchedules) { Name = "Scheduler.CheckSchedules" };
            _thread.Start();
        }


        public void Stop()
        {
            _terminated = true;
            if (!_thread.Join(1000))
            {
                _thread.Abort();
            }
        }

        #endregion

        #region Implementation

        private void checkSchedules()
        {
            try
            {
                // RriThreadUtil.RegisterThread();

                _lastCheck = DateTime.Now;

                while (!_terminated)
                {
                    // Tutto questo giro serve per assicurarsi che non si perdano dei
                    // secondi tra un check e l'altro.
                    // 
                    DateTime current = _lastCheck;
                    DateTime now = _lastCheck = DateTime.Now;

                    while (current < now)
                    {
                        current += ONE_SECOND;

                        ScheduleSelection ss = new ScheduleSelection(current);

                        lock (_schedules)
                        {
                            foreach (Schedule schedule in _schedules)
                            {
                                if (schedule.IsValid(current))
                                {
                                    ss.AddSchedule(schedule);
                                }
                            }
                        }

                        // l'esecuzione dei task avviene in un thread separato per permettere
                        // la corretta continuazione delle regole
                        if (ss.IsValid)
                        {
                            //Thread execute = new Thread(executeSchedules) { Name = String.Format("Scheduler.ExecuteSchedules[{0}]", ++idThread) };
                            //execute.Start(ss);
                            ThreadPool.QueueUserWorkItem(executeSchedules, ss);
                        }
                    }

                    Thread.Sleep(0);
                    Thread.Sleep(1000);
                }
            }
            finally
            {
                // RriThreadUtil.UnregisterThread();
            }
        }

        /// <summary>
        /// Deve controllare che il controllo sulle schedulazioni avvenga per OGNI secondo
        /// </summary>
        /// <param name="arg"></param>
        private void executeSchedules(Object arg)
        {
            try
            {
                Thread.CurrentThread.Name = "Scheduler.ExecuteSchedules";
                // RriThreadUtil.RegisterThread();

                ScheduleSelection ss = (ScheduleSelection)arg;
                DateTime now = ss.Now;
                ScheduleContext context = new ScheduleContext(this, now);

                foreach (Schedule s in ss.Schedules)
                {
                    Trigger trigger = s.SearchTrigger(now);
                    if (trigger == null)
                        continue;

                    context.SetTrigger(trigger);

                    try
                    {
                        s.Execute(context);
                    }
                    catch (Exception e)
                    {
                        _logger.Error(e, e);
                    }
                }
            }
            finally
            {
                // RriThreadUtil.UnregisterThread();
            }
        }

        #endregion
    }


    // ======================================================================
    // StringDictionary Extension
    // ======================================================================

    public static class StringDictionaryExtension
    {
        public static String GetParameter(this StringDictionary dict, String name, String defaultValue)
        {
            return dict.ContainsKey(name) ? dict[name] : defaultValue;
        }

        public static int GetParameter(this StringDictionary dict, String name, int defaultValue)
        {
            return dict.ContainsKey(name) ? int.Parse(dict[name]) : defaultValue;
        }

        public static bool GetParameter(this StringDictionary dict, String name, bool defaultValue)
        {
            return dict.ContainsKey(name) ? bool.Parse(dict[name]) : defaultValue;
        }

        public static double GetParameter(this StringDictionary dict, String name, double defaultValue)
        {
            return dict.ContainsKey(name) ? double.Parse(dict[name]) : defaultValue;
        }
    }

}
